<template>
    <div v-if="easyFlowVisible"  @mousedown.stop="CloseOptionPanel" style="height: calc(100vh);">
        <el-row ref="Bar" class="Bar">
            <!--顶部工具菜单-->
            <el-col :span="24">
                <div class="ef-tooltar">
                    <el-link type="primary" :underline="false">{{data.name}}</el-link>
                    <el-divider direction="vertical"></el-divider>
                    <el-button type="text" icon="el-icon-delete" size="large" @click="deleteElement" :disabled="!this.activeElement.type"></el-button>
                    <el-divider direction="vertical"></el-divider>
                    <el-button type="text" icon="el-icon-download" size="large" @click="downloadData"></el-button>
                    <el-divider direction="vertical"></el-divider>
                    <el-button type="text" icon="el-icon-plus" size="large" @click="zoomAdd"></el-button>
                    <el-divider direction="vertical"></el-divider>
                    <el-button type="text" icon="el-icon-minus" size="large" @click="zoomSub"></el-button>
                    <div style="float: right;margin-right: 5px">
                        <el-button type="info" plain round icon="el-icon-document" @click="dataInfo" size="mini">流程信息</el-button>
                         <!-- <el-button type="primary" plain round icon="el-icon-video-play" size="mini"  @click="$router.push({ path: '/run' })">运行模式</el-button>
                        <el-button type="primary" plain round :class="{'active':ChangeActive(1)}" @click="dataReloadA" icon="el-icon-refresh" size="mini">切换流程A</el-button>
                        <el-button type="primary" plain round :class="{'active':ChangeActive(2)}" @click="dataReloadB" icon="el-icon-refresh" size="mini">切换流程B</el-button>
                        <el-button type="primary" plain round :class="{'active':ChangeActive(3)}" @click="dataReloadC" icon="el-icon-refresh" size="mini">切换流程C</el-button>
                        <el-button type="primary" plain round :class="{'active':ChangeActive(4)}" @click="dataReloadD" icon="el-icon-refresh" size="mini">自定义样式</el-button>
                        <el-button type="primary" plain round :class="{'active':ChangeActive(5)}" @click="dataReloadE" icon="el-icon-refresh" size="mini">力导图</el-button> -->
                        <el-button type="primary" plain round :class="{'active':ChangeActive(6)}" @click="SavePanel('保存成功')" icon="el-icon-s-claim" size="mini">保存</el-button>
                        <el-button type="primary" plain round icon="el-icon-upload" size="mini" @click="releaseProcess">发布</el-button>
                        <el-button type="primary" plain round icon="el-icon-back" size="mini" @click="$router.push('/home')">返回</el-button>
                        <el-button type="info" plain round icon="el-icon-document" @click="openHelp" size="mini">帮助</el-button>
                    </div>
                </div>
            </el-col>
        </el-row>
        <div class="ContainerBody" ref="ContainerBody">
            <div ref="SilderBar" @click="ShowEditPanel = false" class="SilderBar" style="width: 230px;border-right: 1px solid #dce3e8;">
                <node-menu @addNode="addNode" ref="nodeMenu"></node-menu>
            </div>
            <!--  @contextmenu.prevent 限制 局部区域不可点击右键 展示自己右键编辑功能 -->
            <!--OptinsNode 参数1 事件对象   参数er 节点对象  参数3 是否是在全局面板右键   --> 
            <div id="efContainer"  @contextmenu.prevent @mousedown="OptinsNode($event,CurrntNode,true)" ref="efContainer" class="container" v-flowDrag>
                <template v-for="node in data.nodeList">
                   <!--OptinsNode 参数1 事件对象   参数er 节点对象  参数3 分为两种 一种是右键节点选择面板 
                   另外一种是 面板右键  传递状态判断 是否是在全局面板右键   -->
                    <flow-node
                            :id="node.id"
                            :key="node.id"
                            :node="node"
                            :activeElement="activeElement"
                            @changeNodeSite="changeNodeSite"
                            @nodeRightMenu="nodeRightMenu"
                            @clickNode="clickNode"
                            @dblclick.native="viewNodeDetail"
                            @mousedown.native.stop="OptinsNode($event,node,false)">
                    </flow-node>
                </template>
                <!-- 给画布一个默认的宽度和高度 -->
                <div id="Container-Size">&nbsp;</div>
                <div class="Option-Panel" ref="OptionPanel" v-show="optionPanelConfig.ShowPanel" 
                :style="{left:`${optionPanelConfig.x}px`,top :`${optionPanelConfig.y}px` , height: `${optionPanelConfig.OptionHeight}px`}">
                  <ul>
                  <li v-for="item in optionPanelConfig.option" @click.stop="OptionMethods($event,item.State)" :key="item.id">
                    <div>
                      <i :class="item.icon"></i>
                      <span>{{item.innerText}}</span>
                    </div>
                    <div class="text">{{item.shortcutsText}}</div>
                  </li>
                </ul>
                </div>
            </div>
            <!-- 右侧表单 -->
            <div class="EditNode" :class="{EditNodeactive:ShowEditPanel}"> 
                <flow-node-form ref="nodeForm" @UpdateNodeState='UpdateNodeState' @sendUpdated='receiveUpdates' @setLineLabel="setLineLabel" @repaintEverything="repaintEverything"></flow-node-form>
            </div>
        </div>
        <!-- 流程数据详情 -->
        <flow-info v-if="flowInfoVisible" ref="flowInfo" :data="data"></flow-info>
        <flow-help v-if="flowHelpVisible" ref="flowHelp"></flow-help>
        <nodedetail v-if="CurrntNode" :showNodedetail='showNodedetail' @close='showNodedetail = false'
         :node="loadNodeinfo()"></nodedetail>

         <el-dialog title="更改线条状态" style="" :visible.sync="changeLineConfig.dialogLineVisible">
            <el-radio-group v-model="changeLineConfig.lineState">
            <el-radio :label="'pedding'">默认状态</el-radio>
            <el-radio :label="'resolve'">成功</el-radio>
            <el-radio :label="'reject'">失败</el-radio>
          </el-radio-group>
          <div class="dialog-bottom" style="margin-top:20px">
            <el-button @click="closeChangeLinedialog">取 消</el-button>
            <el-button type="primary" @click="confirmChangeLinedialog">确 定</el-button>
          </div>
         </el-dialog>
        
    </div>

</template>

<script>
// bug  起始节点无法查看详情 （在其他节点点击过来的时候 无法查看）
    import draggable from 'vuedraggable'
    // import { jsPlumb } from 'jsplumb'
    // 使用修改后的jsplumb
    import './jsplumb'
    
    import { easyFlowMixin } from '@/components/ef/mixins'
    import flowNode from '@/components/ef/node'
    import nodeMenu from '@/components/ef/node_menu'
    import FlowInfo from '@/components/ef/info'
    import FlowHelp from '@/components/ef/help'
    import FlowNodeForm from './node_form'
    import lodash from 'lodash'
    // import { getDataA } from './data_A'
    // import { getDataB } from './data_B'
    // import { getDataC } from './data_C'
    // import { getDataD } from './data_D'
    // import { getDataE } from './data_E'
    // import { ForceDirected } from './force-directed'
    import axios from 'axios'
    import { getProcessList } from '@/Services/Process'
    import nodedetail from '@/components/ef/node_detail.vue'
    import html2canvas from 'html2canvas'
    import canvg from 'canvg'
    import optionEnum from '@/components/ef/constant'

    export default {
        name : 'panel',
        data() {
            return {
                // jsPlumb 实例
                jsPlumb: null,
                // 控制画布销毁
                easyFlowVisible: true,
                // 控制流程数据显示与隐藏
                flowInfoVisible: false,
                // 是否加载完毕标志位
                loadEasyFlowFinish: false,
                flowHelpVisible: false,
                // 数据
                data: {},
                // 激活的元素、可能是节点、可能是连线
                activeElement: {
                    // 可选值 node 、line
                    type: undefined,
                    // 节点ID
                    nodeId: undefined,
                    // 连线ID
                    sourceId: undefined,
                    targetId: undefined
                },
                zoom: 0.5,
                //当前点击的流程图面板
                CurrentProcessPanel: {
                  id: undefined,
                  name: undefined
                }, 
                //当前点击的节点信息
                CurrntNode:null,
                // 选项面板的配置项的值
                optionPanelConfig:{
                /**
                 * innerText 面板展示的文字信息
                 * shortcutsText 快捷键展示信息
                 * State 每个选项的状态 ----->需要根据此状态来判断用户执行的操作
                 * 选项配置 又分为  节点选项 和 面板选项
                 */
                  option:[],
                  //节点选项
                  NodeOptions:[
                    {
                      id: 7,
                      icon:'el-icon-scissors',
                      innerText:'剪切',
                      shortcutsText:'Ctrl+X',
                      State: optionEnum.SHEAR_STATE
                    },
                    {
                      id: 5,
                      icon:'el-icon-document-copy',
                      innerText:'复制',
                      shortcutsText:'Ctrl+C',
                      State:optionEnum.COPY_STATE
                    },
                    {
                      id: 6,
                      icon:'el-icon-document-add',
                      innerText:'粘贴',
                      shortcutsText:'Ctrl+V',
                      State:optionEnum.PASTE_STATE
                    },
                    {
                      id: 1,
                      icon:'el-icon-edit-outline',
                      innerText:'编辑节点', 
                      shortcutsText:'Ctrl+D',
                      State:optionEnum.EDIT_STATE
                    },
                    {
                      id: 2,
                      icon:'el-icon-delete',
                      innerText:'删除',
                      shortcutsText:'delete',
                      State:optionEnum.DELETE_STATE
                    },
                    {
                      id: 3,
                      icon: 'el-icon-view',
                      innerText:'查看',
                      shortcutsText:'Ctrl+Q',
                      State:optionEnum.PREVIEW_STATE
                    },
                      {
                      id: 4,
                      icon: 'el-icon-refresh-left',
                      innerText:'刷新',
                      shortcutsText:'F5',
                      State:optionEnum.REFRESH_STATE
                    }
                  ],
                  PanelOptions:[
                      {
                      id: 1,
                      icon:'el-icon-document-add',
                      innerText:'粘贴',
                      shortcutsText:'Ctrl+V',
                      State: optionEnum.PASTE_STATE
                    },
                    {
                      id: 2,
                      icon:'el-icon-s-data',
                      innerText:'更改状态',
                      shortcutsText:'Ctrl+R',
                      State: optionEnum.REPLACE_STATE
                    },
                  ],
                  // 是否展示面板
                  ShowPanel :false,
                  OptionHeight: 220,
                  x : 0,
                  y : 0
                },
                // 保存的状态
                SaveState : true,
                //编辑面板的状态
                ShowEditPanel:false,
                // 复制功能的配置选项
                copyOptionConfig : {
                  //是否是 粘贴选项过来的
                  X: 0 ,
                  Y: 0,
                  isShear: false,
                  copyCurrentNode:[]
                },
                // 线条状态
                changeLineConfig:{
                  lineState: 'pedding',
                  // 更改线条状态 dialog 决定是否显示
                  dialogLineVisible : false,
                  //是否选中线条 选中了才能打开更改状态的面板
                  isSelectedLine: false, 
                  // 当前线条开始节点
                  from : undefined,
                  //当前线条结束节点
                  to : undefined
                },
                //展示节点详情
                showNodedetail: false,
                // 撤销 Stack 栈
                recordStack:[],
                // 缩略图base 64
                thumbnail:'',
                
                
            }
        },
        // 一些基础配置移动该文件中
        mixins: [easyFlowMixin],
        components: {
            draggable, flowNode, nodeMenu, FlowInfo, FlowNodeForm, FlowHelp,nodedetail
        },
        directives: {
            'flowDrag': {
                bind(el, binding, vnode, oldNode) {
                    if (!binding) {
                        return
                    }
                    el.onmousedown = (e) => {
                        if (e.button == 2) {
                            // 右键不管
                            return
                        }
                        //  鼠标按下，计算当前原始距离可视区的高度
                        let disX = e.clientX
                        let disY = e.clientY
                        el.style.cursor = 'move'

                        document.onmousemove = function (e) {
                            // 移动时禁止默认事件
                            e.preventDefault()
                            const left = e.clientX - disX
                            disX = e.clientX
                            el.scrollLeft += -left

                            const top = e.clientY - disY
                            disY = e.clientY
                            el.scrollTop += -top
                        }

                        document.onmouseup = function (e) {
                            el.style.cursor = 'auto'
                            document.onmousemove = null
                            document.onmouseup = null
                        }
                    }
                }
            }
        },
        mounted() {
            this.jsPlumb =  jsPlumb.getInstance()
            // this.$nextTick(() => {
            //     // 默认加载流程A的数据、在这里可以根据具体的业务返回符合流程数据格式的数据即可
            //     this.dataReload(getDataB())
            // }) 
        },
        created(){
          const { id , name}  = this.$route.query
          // 转换 路由参数

          this.LoadPanelInfo(parseInt(id) , decodeURI(name))
          // 初始化快捷键事件
          this.Shortcuts()
        },
        activated:{

        },
        methods: {
            getUUID() {
                return Math.random().toString(36).substr(3, 10)
            },
            jsPlumbInit() {
                this.jsPlumb.ready(() => {
                    // 导入默认配置
                    this.jsPlumb.importDefaults(this.jsplumbSetting)
                    // 会使整个jsPlumb立即重绘。
                    this.jsPlumb.setSuspendDrawing(false, true);
                    // 初始化节点
                    this.loadEasyFlow()
                    // 单点击了连接线, https://www.cnblogs.com/ysx215/p/7615677.html
                    this.jsPlumb.bind('click', (conn, originalEvent) => {
                        this.activeElement.type = 'line'
                        this.activeElement.sourceId = conn.sourceId
                        this.activeElement.targetId = conn.targetId
                        this.$refs.nodeForm.lineInit({
                            from: conn.sourceId,
                            to: conn.targetId,
                            label: conn.getLabel()
                        })
                    })
                    // 连线  --->初始化线的颜色
                    this.jsPlumb.bind("connection", (evt) => {
                        let from = evt.source.id
                        let to = evt.target.id
                        if (this.loadEasyFlowFinish) {
                            this.data.lineList.push({from: from, to: to , State:'pedding'})
                        }
                        // this.LoadPanelInfo(this.CurrentProcessPanel.id , this.CurrentProcessPanel.name)
                    })

                    // 删除连线回调
                    this.jsPlumb.bind("connectionDetached", (evt) => {
                        this.deleteLine(evt.sourceId, evt.targetId)
                    })

                    // 改变线的连接节点
                    this.jsPlumb.bind("connectionMoved", (evt) => {
                        this.changeLine(evt.originalSourceId, evt.originalTargetId)
                    })

                    // 连线右击
                    this.jsPlumb.bind("contextmenu", (evt) => {
                      const { sourceId , targetId } = evt
                      if(!Object.keys(evt).length) return
                      this.changeLineConfig.from = sourceId
                      this.changeLineConfig.to = targetId
                      //能走下来证明你已经选了线条
                      this.changeLineConfig.isSelectedLine = true
                      this.changeLineConfig.lineState =  this.data.lineList.filter(item =>( item.from === sourceId && item.to === targetId ))[0].State;


                    })
                    this.jsPlumb.setContainer(this.$refs.efContainer)
                    // 限制拖动的区域
                    // this.jsPlumb.draggable(this.$refs.efContainer, {
                    //   containment: '',
                    //   grid: [50, 50]
                    // })

                    // 连线
                    this.jsPlumb.bind("beforeDrop", (evt) => {
                        let from = evt.sourceId
                        let to = evt.targetId
                        if (from === to) {
                            this.$message.error('节点不支持连接自己')
                            return false
                        }
                        if (this.hasLine(from, to)) {
                            this.$message.error('该关系已存在,不允许重复创建')
                            return false
                        }
                        if (this.hashOppositeLine(from, to)) {
                            this.$message.error('不支持两个节点之间连线回环');
                            return false
                        }
                        this.$message.success('连接成功')
                        return true
                    })

                    // beforeDetach
                    this.jsPlumb.bind("beforeDetach", (evt) => {
                        console.log('beforeDetach', evt)
                    })
                    this.jsPlumb.setContainer(this.$refs.efContainer)
                })
            },
            // 加载流程图
            loadEasyFlow() {
                // 初始化节点
                for (var i = 0; i < this.data.nodeList.length; i++) {
                    let node = this.data.nodeList[i]
                    // 设置源点，可以拖出线连接其他节点
                    this.jsPlumb.makeSource(node.id, lodash.merge(this.jsplumbSourceOptions, {}))
                    // // 设置目标点，其他源点拖出的线可以连接该节点
                    this.jsPlumb.makeTarget(node.id, this.jsplumbTargetOptions)
                    if (!node.viewOnly) {
                        this.jsPlumb.draggable(node.id, {
                            containment: 'parent',
                            stop: function (el) {
                                // 拖拽节点结束后的对调
                                // console.log('拖拽结束: ', el)
                            }
                        })
                    }
                }
                // 初始化连线
                for (var i = 0; i < this.data.lineList.length; i++) {
                    let line = this.data.lineList[i]
                    var connParam = {
                        source: line.from,
                        target: line.to,
                        label: line.label ? line.label : '',
                        connector: line.connector ? line.connector : '',
                        anchors: line.anchors ? line.anchors : undefined,
                        paintStyle: line.paintStyle ? line.paintStyle : undefined,
                        paintStyle: { stroke: this.setLineColor(line.from,line.to), strokeWidth: 3 },
                    }
                    this.jsPlumb.connect(connParam, this.jsplumbConnectOptions)
                }
                this.$nextTick(function () {
                    this.loadEasyFlowFinish = true
                })
            },
            // 设置 连线的颜色  
            setLineColor (from , to) {
              // 如果
             try {
              const lineState = this.data.lineList.filter(item =>item.State).find(item => (item.from === from && item.to === to)) 
              if(lineState.State === 'pedding') return 'gray' || "gray"
              if(lineState.State === 'resolve') return "green"|| "gray"
              if(lineState.State === 'reject')  return "red"  || "gray"
             } catch (error) {
              // console.log(error);
             }
            //  return
            },
            // 设置连线条件
            setLineLabel(from, to, label) {
                var conn = this.jsPlumb.getConnections({
                    source: from,
                    target: to
                })[0]
                if (!label || label === '') {
                    conn.removeClass('flowLabel')
                    conn.addClass('emptyFlowLabel')
                } else {
                    conn.addClass('flowLabel')
                }
                conn.setLabel({
                    label: label,
                })
                this.data.lineList.forEach(function (line) {
                    if (line.from == from && line.to == to) {
                        line.label = label
                    }
                })

            },
            // 删除激活的元素
            deleteElement() {
                if (this.activeElement.type === 'node') {
                    this.deleteNode(this.CurrntNode)
                } else if (this.activeElement.type === 'line') {
                    this.$confirm('确定删除所点击的线吗?', '提示', {
                        confirmButtonText: '确定',
                        cancelButtonText: '取消',
                        type: 'warning'
                    }).then(() => {
                        var conn = this.jsPlumb.getConnections({
                            source: this.activeElement.sourceId,
                            target: this.activeElement.targetId
                        })[0]
                        this.jsPlumb.deleteConnection(conn)
                    }).catch(() => {
                    })
                }
            },
            // 删除线
            deleteLine(from, to) {
                this.data.lineList = this.data.lineList.filter(function (line) {
                    if (line.from == from && line.to == to) {
                        return false
                    }
                    return true
                })
            },
            // 改变连线
            changeLine(oldFrom, oldTo) {
                this.deleteLine(oldFrom, oldTo)
            },
            // 改变节点的位置
            changeNodeSite(data) {
                for (var i = 0; i < this.data.nodeList.length; i++) {
                    let node = this.data.nodeList[i]
                    if (node.id === data.nodeId) {
                        node.left = data.left
                        node.top = data.top
                    }
                }
            },
            //初始化节点可拖动 可随自身移动
            NodePositioninit (nodeId) {
              this.jsPlumb.makeSource(nodeId, this.jsplumbSourceOptions)
              this.jsPlumb.makeTarget(nodeId, this.jsplumbTargetOptions)
              this.jsPlumb.draggable(nodeId, {
               containment: 'parent',
                  stop: function (el) {
                     // 拖拽节点结束后的对调
                     // console.log('拖拽结束: ', el)
                   }
                })
            },
            /**
             * 拖拽结束后添加新的节点
             * @param evt
             * @param nodeMenu 被添加的节点对象
             * @param mousePosition 鼠标拖拽结束的坐标
             */
            addNode(evt, nodeMenu, mousePosition) {
                var screenX = evt.originalEvent.clientX, screenY = evt.originalEvent.clientY
                let efContainer = this.$refs.efContainer
                var containerRect = efContainer.getBoundingClientRect()
                var left = screenX, top = screenY
                // 计算是否拖入到容器中
                if (left < containerRect.x || left > containerRect.width + containerRect.x || top < containerRect.y || containerRect.y > containerRect.y + containerRect.height) {
                    this.$message.error("请把节点拖入到画布中")
                    return
                }
                left = left - containerRect.x + efContainer.scrollLeft
                top = top - containerRect.y + efContainer.scrollTop
                // 居中
                left -= 85
                top -= 16
                var nodeId = this.getUUID()
                // 动态生成名字.
                var origName = nodeMenu.name
                var nodeName = origName
                var index = 1
                while (index < 10000) {
                    var repeat = false
                    for (var i = 0; i < this.data.nodeList.length; i++) {
                        let node = this.data.nodeList[i]
                        if (node.name === nodeName) {
                            nodeName = origName + index
                            repeat = true
                        }
                    }
                    if (repeat) {
                        index++
                        continue  
                    }
                    break
                }
                var node = {
                    id: nodeId,
                    name: nodeName,
                    type: nodeMenu.type,
                    left: left + 'px',
                    top: top + 'px',
                    ico: nodeMenu.ico,
                    state: 'success',
                    CreateTime:Date.now()
                }
                /**
                 * 这里可以进行业务判断、是否能够添加该节点
                 */
                this.data.nodeList.push(node)
                this.$nextTick(function () {this.NodePositioninit(nodeId)})
            },
            /**
             * 删除节点
             * @param nodeId 被删除节点的ID
             */
           async deleteNode(NodeInfo) {
              const result =  await this.$confirm('确定要删除节点---' + NodeInfo.name + '?', '提示', {
                    confirmButtonText: '确定',
                    cancelButtonText: '取消',
                    type: 'warning',
                    closeOnClickModal: false}).catch(e=>e)
                    if(result !== 'confirm') return
                    this.data.nodeList = this.data.nodeList.filter((node)=>(node.id !== NodeInfo.id))
                    this.data.lineList = this.data.lineList.filter(item => (item.from !== NodeInfo.id && item.to !== NodeInfo.id))
                    // await this.jsPlumb.remove(NodeInfo.id)
                     this.$nextTick(function () {
                        this.jsPlumb.removeAllEndpoints(NodeInfo.id);
                    })
                    this.SavePanel('删除成功')
                   
                return true
            },
            // 事件第一个参数就是节点id
            clickNode(NodeInfo) {
               //跟随鼠标位置移动
               window.addEventListener('click' ,(e) =>  {
                const { clientX , clientY} = e
                try {
                this.optionPanelConfig.x = (clientX - this.$refs.SilderBar.clientWidth) || 230
                this.optionPanelConfig.y = (clientY - this.$refs.Bar._vnode.elm.clientHeight) || 42
                } catch (error) {
                  
                }
               })

                this.activeElement.type = 'node'
                this.activeElement.nodeId = NodeInfo
                this.$refs.nodeForm.nodeInit(this.data, NodeInfo)
            },
            // 是否具有该线
            hasLine(from, to) {
                for (var i = 0; i < this.data.lineList.length; i++) {
                    var line = this.data.lineList[i]
                    if (line.from === from && line.to === to) {
                        return true
                    }
                }
                return false
            },
            // 是否含有相反的线
            hashOppositeLine(from, to) {
                return this.hasLine(to, from)
            },
            nodeRightMenu(nodeId, evt) {
                this.menu.show = true
                this.menu.curNodeId = nodeId
                this.menu.left = evt.x + 'px'
                this.menu.top = evt.y + 'px'
            },
            repaintEverything() {
            
                this.jsPlumb.repaint()
                this.SavePanel()
                // this.SaveAll()
            },
            // 流程数据信息
            dataInfo() {
                this.flowInfoVisible = true
                this.$nextTick(function () {
                    this.$refs.flowInfo.init()
                })
            },
            // 加载流程图
            dataReload(data) {
               this.easyFlowVisible = false
                this.data.nodeList = []
                this.data.lineList = []
                this.$nextTick(() => {
                    data = lodash.cloneDeep(data)
                    this.easyFlowVisible = true
                    this.data = data
                    this.$nextTick(() => {
                        this.jsPlumb = jsPlumb.getInstance()
                        this.$nextTick(() => {
                            this.jsPlumbInit()
                        })
                    })
                })
            },
          //   // 模拟载入数据dataA
          //   dataReloadA() {
          //       // this.dataReload(getDataA())
          //      this.LoadPanelInfo(1,'流程A')
          //   },
          //   // 模拟载入数据dataB
          //   dataReloadB() {
          //       // this.dataReload(getDataB())
          //       this.LoadPanelInfo(2,'流程B')
          //   },
          //   // 模拟载入数据dataC
          //  dataReloadC() {
          //       this.LoadPanelInfo(3,'流程C')
          //   },
          //   // 模拟载入数据dataD
          //   dataReloadD() {
          //       this.LoadPanelInfo(4,'流程D')
          //   },
            // 模拟加载数据dataE，自适应创建坐标
            // dataReloadE() {
                // let dataE = getDataE()
                // let tempData = lodash.cloneDeep(dataE)
                // let data = ForceDirected(tempData)
                // this.dataReload(data)
                // this.$message({
                //     message: '力导图每次产生的布局是不一样的',
                //     type: 'warning'
                // });
            // },
            zoomAdd() {
                if (this.zoom >= 1) {
                    return
                }
                this.zoom = this.zoom + 0.1
                this.$refs.efContainer.style.transform = `scale(${this.zoom})`
                this.jsPlumb.setZoom(this.zoom)
            },
            zoomSub() {
                if (this.zoom <= 0) {
                    return
                }
                this.zoom = this.zoom - 0.1
                this.$refs.efContainer.style.transform = `scale(${this.zoom})`
                this.jsPlumb.setZoom(this.zoom)
            },
            // 下载数据
            downloadData() {
                this.$confirm('确定要下载该流程数据吗？', '提示', {
                    confirmButtonText: '确定',
                    cancelButtonText: '取消',
                    type: 'warning',
                    closeOnClickModal: false
                }).then(() => {
                    var datastr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(this.data, null, '\t'));
                    var downloadAnchorNode = document.createElement('a')
                    downloadAnchorNode.setAttribute("href", datastr);
                    downloadAnchorNode.setAttribute("download", 'data.json')
                    downloadAnchorNode.click();
                    downloadAnchorNode.remove();
                    this.$message.success("正在下载中,请稍后...")
                }).catch(() => {
                })
            },
            openHelp() {
                this.flowHelpVisible = true
                this.$nextTick(function () {
                    this.$refs.flowHelp.init()
                })
            },

            /**
            * @description: 返回当前节点信息 和 当前面板信息，传递到节点详情组件中使用
            * @param {type} 
            * @return: 返回值为 当前面板信息 和当前节点信息
            */
            loadNodeinfo () {
              const { name , id  } = this.CurrentProcessPanel
              return {...this.CurrntNode , ...{Panelname:name,PanelID:id}}
            },
            /**
            * @description: 当节点选中并编辑完成后 需要更新当前节点的信息
            * @param {type} 参数为 当前节点对象
            * @return: 无返回值
            */
            UpdateNodeState (val) { 
             this.data.nodeList =  this.data.nodeList.map(item =>(item.id === val.id ? val.node : item ))
            },

            /**
            * @description: 加载面板信息 根据 面板的 id 来获取面板节点数据
            * @param {type} id 面板id , name为 面板名称
            * @return: 返回值为 当前面板的信息结果
            */
           async LoadPanelInfo (id , name ) {
                this.CurrentProcessPanel.id = id
                this.CurrentProcessPanel.name = name
                const res = await getProcessList(this.CurrentProcessPanel.id)
                await this.dataReload(res.data[0])

            },
            /**
            * @description:保存当前面板，将最新的状态发送请求更新数据
            * @param {type} message 传递的消息字符串，用于更新成功提示
            * @return:  无返回值
            */
           async SavePanel(message){
              // 如果你已经保存了 就不要继续保存了
              // if(this.SaveState) return
              await axios.put(`/Process/${this.CurrentProcessPanel.id}`,{
              id:`${this.CurrentProcessPanel.id}`,
              name:this.CurrentProcessPanel.name,
              thumbnail:this.thumbnail,
              nodeList:this.data.nodeList,
              lineList:this.data.lineList
              })
              message ? this.$message.success(message) : ''
              // this.dataReloadB()
              // this.LoadPanelInfo(this.CurrentProcessPanel.id ,this.CurrentProcessPanel.name)
            },
          /**
          * @description:发布 当前的流程图 ，并且保存当前流程图
          * @param {type} 
          * @return: 无返回值
          */
           async releaseProcess(){
              // 生成缩略图
              await this.createContainerImg()
              //发布的同时保存一下面板
              this.SaveAll('发布成功')
              this.$router.push('/home')
            },
            /**
            * @description: 顶部导航栏按钮 动态高亮
            * @param {type} 
            * @return: 无返回值
            */
            ChangeActive (id) {
              return this.CurrentProcessPanel.id === id
            },

            /**
            * @description:选项节点的位置信息以及宽高度的动态展示
            * @param {type} 
            * @return: 无返回值
            */
            OptinsNode(e,node,State){
              //保存节点信息,编辑时需要数据回填
              this.CurrntNode = node
              // 保存 粘贴的鼠标右键按下的位置
              const { clientX , clientY } = e
              this.copyOptionConfig.X = clientX
              this.copyOptionConfig.Y = clientY
              if(e.button !== 2) return
           // 根据状态来决定 你是 节点选项面板 还是 全局选项面板
              const { NodeOptions , PanelOptions } = this.optionPanelConfig
              if(State){
                this.optionPanelConfig.option = PanelOptions;
                this.optionPanelConfig.OptionHeight = 50
              }
              else{
                this.optionPanelConfig.option = NodeOptions
                this.optionPanelConfig.OptionHeight = 220
              }
            
              this.optionPanelConfig.ShowPanel = true
         
              //设置右键点击时 选项列表的位置
              this.optionPanelConfig.x = (clientX - this.$refs.SilderBar.clientWidth) || 230
              this.optionPanelConfig.y = (clientY - this.$refs.Bar._vnode.elm.clientHeight) || 42
            },
            /**
            * @description:关闭 自定义面板(右键的选项配置)
            * @param {type} 
            * @return: 无返回值
            */
            CloseOptionPanel(e){
              
              const MinX = this.optionPanelConfig.x + (this.$refs.SilderBar.clientWidth || 230)
              const MaxX = MinX + this.$refs.OptionPanel.clientWidth
              // 鼠标点击左键关闭 配置列表
              if((e.button === 0 && (e.clientX >= MinX && e.clientX <= MaxX )))return
              if(e.button === 0){
                // this.ShowEditPanel = false
                this.optionPanelConfig.ShowPanel = false
                //鼠标左键 点击其他地方 将上一次右键获取的线信息清除
                // this.clearLastLineinfo()

              }
      
            },
            /**
            * @description: 快捷键操作 ，在生命周期创建时调用 ，监听用户按下的键
            * @param {type} 
            * @return: 无返回结果
            */
            Shortcuts(){
              let $Event = undefined
              document.addEventListener('keydown', (e)=>{
                  $Event = e
                  const keyCode = $Event.key.toLocaleUpperCase()
                  const ctrlKey = $Event.ctrlKey || e.metaKey;
                  const ShortcuGroupKey = {keyCode,ctrlKey}
                  CtrlS(ShortcuGroupKey)
                  CtrlD(ShortcuGroupKey)
                  CtrlC(ShortcuGroupKey)
                  CtrlV(ShortcuGroupKey)
                  CtrlX(ShortcuGroupKey)
                  CtrlQ(ShortcuGroupKey)
                  CtrlZ(ShortcuGroupKey);
                  Delete(keyCode)
                  
              })
              /**
              * @description:取消 浏览器事件的默认行为
              * @param {type} 
              * @return: 
              */
             const CancelPreventDefault = () => $Event.preventDefault()

             /**
             * @description: 快捷键方法抽离 给该方法传递一个 按住键的配置对象 和需要执行的方法
             * @param {type} 
             * @return: 返回 执行传入函数
             */
             const ShortcuCtrls = (val,fn) => {
              const {ctrlKey , keyCode , ShortcuKey} = val
              if(ctrlKey && keyCode === ShortcuKey){
                fn.apply(this)

                CancelPreventDefault()
              }
             }
             const CtrlS = (val) => ShortcuCtrls({...val,ShortcuKey : 'S'},this.SaveAll)
             const CtrlD = (val) => ShortcuCtrls({...val,ShortcuKey : 'D'},this.EditNodeInfo)
             const CtrlC = (val) => ShortcuCtrls({...val,ShortcuKey : 'C'},this.copyNode)
             const CtrlV = (val) => ShortcuCtrls({...val,ShortcuKey : 'V'},this.pasteNode)
             const CtrlX = (val) => ShortcuCtrls({...val,ShortcuKey : 'X'},this.shearNode)
             const CtrlQ = (val) => ShortcuCtrls({...val,ShortcuKey : 'Q'},this.viewNodeDetail)
             const CtrlZ = (val) => ShortcuCtrls({...val,ShortcuKey : 'Z'},this.revokeLastOperation)
             const Delete = (keyCode) => {if(keyCode === 'DELETE'){this.deleteNode(this.CurrntNode || {}),CancelPreventDefault()}}

            },

            /**
            * @description:选项的各个方法 ，右键打开选项面板 通过switch case 来确认用户点击的是哪个
            * @param {type} 
            * @return:  没有返回值 
            */
            OptionMethods(e,State){
              switch(State){
                //编辑节点
                case optionEnum.EDIT_STATE:
                this.EditNodeInfo();
                break
                // 删除节点
                case optionEnum.DELETE_STATE:
                this.deleteNode(this.CurrntNode || {});
                break
                // 刷新页面
                case optionEnum.REFRESH_STATE:
                window.location.reload();
                break
                case optionEnum.COPY_STATE :
                // 复制节点 
                this.copyNode();
                break
                // 粘贴 节点
                case optionEnum.PASTE_STATE:
                this.pasteNode(e)
                break
                 // 剪切 节点
                case optionEnum.SHEAR_STATE :
                this.shearNode()
                break
                //查看节点信息
                case optionEnum.PREVIEW_STATE :
                this.viewNodeDetail()
                break
                case optionEnum.REPLACE_STATE :
                this.changeLineState()
                break
              }
              this.optionPanelConfig.ShowPanel = false
            },
             /**
             * @description: 用户编辑节点 信息
             * @param {type} 
             * @return: 
             */
              EditNodeInfo () {
              this.clickNode(this.CurrntNode.id || '')
              this.ShowEditPanel = true
              },
              /**
              * @description: 保存全局的状态
              * @param {type} 
              * @return: 
              */
              SaveAll(message){
                message ? message : message = '保存成功'
               try {
                if(this.SaveState)return
                // 每次保存调用一下子组件保存的方法
                this.$refs.nodeForm.save()
                this.SavePanel(message)
                this.SaveState = true
               } catch (error) {}
              },
              /**
              * @description: 更改节点的定位 由于粘贴 可能是 ctrl + c 渠道来的 
              * 还有可能是 来自于剪切 
              * 如果是从 ctrl +c 出来的 会默认向下 和向右 偏移 25  15 像素
              * @param {type} 
              * 如果是 复制出来的 沿着 x轴 偏移 15像素 Y轴 偏移 25
              * 如果是 剪切过来的 则是当前的鼠标右键点击的位置  
              * @return: 返回一个节点坐标对象
              */
              ChangeNodePosition (X = 15 , Y = 25) {
                /** 取出复制节点的 left 和 top 值并加 几像素 避免 重叠 */
                const { left , top } = this.CurrntNode 
                const { isShear } = this.copyOptionConfig
                if(!left && !top) return
                let index = undefined;
                let positionleft = undefined;
                let positiontop = undefined;
                // 边界判断 如果是 数字 直接加15像素
                if(typeof left === 'number' && top === 'number'){ 
                  positionleft = isShear ? X : left + X , positiontop = isShear ? Y : top + Y
                  }

                if(typeof left === 'string' &&  typeof top === 'string' ){
                index = left.indexOf('px')
                positionleft = isShear ? `${X - this.$refs.SilderBar.clientWidth || 230}px` : (parseInt(left.substring(0 , index)) + X) + "px" 
                positiontop = isShear ? `${Y - this.$refs.Bar._vnode.elm.clientHeight || 42}px` : (parseInt(top.substring(0 , index)) + Y) + "px" 
                }
                return {positionleft, positiontop}
              },

              /**
              * @description: 复制 节点 ctrl + c 
              * @param {type} 
              * @return: 
              */
              copyNode(){
                const {positionleft , positiontop} = this.ChangeNodePosition( 15, 25)
                this.copyOptionConfig.copyCurrentNode.push({...this.CurrntNode,...{id: this.getUUID(),left: positionleft,top : positiontop}})
              },


              /**
              * @description: 粘贴 复制的节点
              * @param {type} 
              * @return: 
              */
              pasteNode(){
                if(!this.copyOptionConfig.copyCurrentNode[0]) return this.$message.warning("暂无可粘贴的选项")
                const { isShear , X , Y } = this.copyOptionConfig
                // 如果是从剪切板过来的 则 重新更新一下位置 否则 正常复制
                if(isShear){
                  const {positionleft , positiontop} = this.ChangeNodePosition(X , Y)
                  this.copyOptionConfig.copyCurrentNode = []
                  this.copyOptionConfig.copyCurrentNode.push({...this.CurrntNode,...{id: this.getUUID(),left: positionleft,top : positiontop}})
                }
                const pasteNodeInfo = this.copyOptionConfig.copyCurrentNode[0]

                this.data.nodeList.push(pasteNodeInfo)

                //加载 粘贴进来的节点 能移动能拖拽
                this.$nextTick(function () {this.NodePositioninit(pasteNodeInfo.id)})  

                //是否想要重复粘贴
                this.copyOptionConfig.copyCurrentNode = []              
              },
              /**
              * @description: 剪切节点操作 
              * @param {type} 
              * @return: 
              */
              shearNode ( a = 1) {
                // 记录未剪切之前的节点和线条
                const {nodeList , lineList} = this.data
                this.recordStack.push ({nodeList,lineList})
                //从剪切板过来
                this.copyOptionConfig.isShear = true
                this.copyNode()
                this.data.nodeList = this.data.nodeList.filter(item => item.id !== this.CurrntNode.id)
                this.data.lineList = this.data.lineList.filter(item => (item.from !== this.CurrntNode.id && item.to !== this.CurrntNode.id))
           
               // 剪切完成后 重新绘制
                this.$nextTick( () => {
                  this.jsPlumb.removeAllEndpoints(this.CurrntNode.id );
                })
                // this.NodePositioninit()

              },

              /**
              * @description:子组件一变化 父组件需要知道变化 以便后面保存
              * @param {type} 
              * @return: 
              */
              receiveUpdates(){
                this.SaveState = false
              },
              /**
              * @description: 预览节点详细信息
              * @param {type} 
              * @return: 
              */
              viewNodeDetail(){
                this.showNodedetail = true
              },
              /**
              * @description: 更改线条状态 
              * @param {type}  'pedding' 代表默认状态
              * 'resolve' 成功状态  reject 失败状态
              * @return: 
              */
              changeLineState () {
                const { isSelectedLine } = this.changeLineConfig
                if(!isSelectedLine) return this.$message.warning("请选择你要更改状态的流程线")
                this.changeLineConfig.dialogLineVisible = true
              },
              //生成缩略图
             async createContainerImg () {
                const el = this.$refs.efContainer;
                //Svgdom数组
                let svgNodesToRemove = [];
                let svgElements = document.body.querySelectorAll('#efContainer svg');
                  //LOOP ON EACH ELEMENT
                svgElements.forEach(function(item) {

                  let children = item.childNodes  
                  children.forEach(function(child) {
                    //解决svg透明度问题
                    child.style.opacity = 1;
                  })

                  let svg = item.outerHTML.trim();

                  let canvas = document.createElement('canvas');

                  canvas.width = item.getBoundingClientRect().width;
                  canvas.height = item.getBoundingClientRect().height;

                  canvg(canvas, svg); 

                  if (item.style.position) {
                      canvas.style.position += item.style.position;
                      canvas.style.left += item.style.left;
                      canvas.style.top += item.style.top;
                  }
                  item.parentNode.appendChild(canvas);

                  svgNodesToRemove.push(canvas)

                });
                const width = el.offsetWidth
	              const height = el.offsetHeight
                let canvas = await html2canvas(el,{
                  useCORS:true,
                  scale: 4,
                  width,
                  height,
                  allowTaint:true,
                  // dpi: window.devicePixelRatio * 4,//设备像素比
                  // windowWidth:document.body.scrollWidth,
                  windowHeight:height,
                  // x:0,
                  // y:window.pageYOffset
                  // backgroundColor: ""
                  })
                this.thumbnail = canvas.toDataURL('images/jpg')
                document.querySelectorAll("#efContainer canvas").forEach(item => {
                  item.remove()
                })
                return true
                
              },
              /**
              * @description:关闭更改 线条状态的dialog
              * @param {type} 
              * @return: 
              */
              closeChangeLinedialog(){
                this.changeLineConfig.isSelectedLine = false
                this.changeLineConfig.dialogLineVisible = false
 
              },

              /**
              * @description: 当 线条状态的dialog 点击确认时执行 该函数 
              * @param {type} 
              * @return: 
              */
             async confirmChangeLinedialog () {
                  const { from , to , lineState } = this.changeLineConfig
                  const { id , name } = this.CurrentProcessPanel
                this.changeLineConfig.isSelectedLine = false
                this.changeLineConfig.dialogLineVisible = false
                 this.data.lineList.forEach(item =>{
                  if(item.from === from && item.to === to){
                    item.State = lineState
                  }
                })
                // console.log(id ,name );
               await this.SavePanel("更改成功")
                this.LoadPanelInfo(id , name)
    
              },
              /**
              * @description: //将上一次右键获取的线信息清除
              * @param {type} 
              * @return: 
              */
              clearLastLineinfo () {
                 this.changeLineConfig = {
                  lineState: 'pedding',
                  // 更改线条状态 dialog 决定是否显示
                  dialogLineVisible : false,
                  //是否选中线条 选中了才能打开更改状态的面板
                  isSelectedLine: false, 
                  // 当前线条开始节点
                  from : undefined,
                  //当前线条结束节点
                  to : undefined
                }
              },
              // 撤回上一次操作
              revokeLastOperation(){}
        },
        watch:{
          data:{
            deep: true,
            immediate:true,
            handler () {
              this.SaveState = false
            }
          },
         
        }
    }
</script>
           
</script>
<style scoped>
.active{
  color: #fff;
  background-color: #409eff;
}
#efContainer{
  /* position: relative; */
  padding: 20px 50px;
}
.Option-Panel{
  position: absolute;
  width: 220px;
  height: 300px;
  z-index: 10;
  padding: 5px 0px;
  background-color: #fff;
  border: 1px solid #b4b4b4;
  box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
}
.Option-Panel ul{
  width: 100%;
  height: 100%;
}
.Option-Panel ul li{
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 25px;
  line-height: 25px;
  box-sizing: border-box;
  padding: 0px 5px;
  cursor: default;
  
  /* background-color: #eeeeee; */
}
.Option-Panel ul li span {
  font-size: 14px;
  margin-left: 8px;
}
.Option-Panel ul li:nth-child(3),.Option-Panel ul li:nth-child(5){
  border-bottom: 1px solid #cccccc;
}
.Option-Panel ul li:hover{
  background-color: #eee;
}
.Option-Panel ul li .text{
  font-size: 13px;
  color: #777;
}

.EditNode{
  position: fixed;
  right: -300px;
  width: 300px;
  height: 100%;
  border-left: 1px solid #dce3e8;
  background-color: #FBFBFB;
  transition: all .4s;

}
.EditNodeactive {
 right:0px
}
.ContainerBody{
  display: flex;
  height: calc(100% - 47px);
}
 #Container-Size{
  position:absolute;
  top: 2000px;
  /* left: 2000px; */
}
.dialog-bottom{
  display: flex;
  justify-content: flex-end;
}
.el-dialog__wrapper >>>.el-dialog{
  width: 30%;
}

</style>
